﻿/*
 * Copyright (c) 2008, Jesse Bowes, Daniel Cousineau, Daniel Duke
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 *  # Redistributions of source code must retain the above copyright notice, this list 
 *      of conditions and the following disclaimer.
 *      
 *  # Redistributions in binary form must reproduce the above copyright notice, this 
 *      list of conditions and the following disclaimer in the documentation and/or    
 *      other materials provided with the distribution.
 *  
 *  # Neither the name of Texas A&M University nor the names of its contributors may be 
 *      used to endorse or promote products derived from this software without specific prior 
 *      written permission.
 *  
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.IO;
using System.Text;
using System.Timers;
using System.Windows.Forms;
using SpeechLib;

namespace SpeechRecognition
{
    public partial class Form1 : Form
    {
        private SpeechLib.SpSharedRecoContext objRecoContext = null;
        private SpeechLib.ISpeechRecoGrammar grammar = null;

        private System.Timers.Timer recogTimer = new System.Timers.Timer(5000);

        private bool recogEnabled = false;
        private bool paused = false;
        private bool repeat = false;
        private bool commandRepeat = false;
        private int volume = 0;
        private WMPLib.IWMPMedia repeatSong = null;

        private string playerXML = "grammars\\player.xml";
        private string albumXML = "grammars\\album.xml";
        private string artistXML = "grammars\\artist.xml";
        private string songXML = "grammars\\song.xml";

        private const short COMMAND = 0;
        private const short SONG = 1;
        private const short ALBUM = 2;
        private const short ARTIST = 3;

        private short listenMode = COMMAND;


        public Form1()
        {
            InitializeComponent();
            generateXML();
            initSpeech();
            initStatus();
            initFormEvents();
        }

        private void initSpeech()
        {
            // Get an insance of RecoContext. I am using the shared RecoContext.
            objRecoContext = new SpeechLib.SpSharedRecoContext();

            // Assign a eventhandler for the Recognition Event.
            objRecoContext.Recognition += new _ISpeechRecoContextEvents_RecognitionEventHandler(Reco_Event);

            //Creating an instance of the grammer object.
            grammar = objRecoContext.CreateGrammar(0);

            loadGrammar(playerXML);
        }

        private void initStatus()
        {
            recogEnabled = false;
            volume = mediaPlayer.settings.volume;
            shuffleLabel.Enabled = mediaPlayer.settings.getMode("shuffle");
            loopLabel.Enabled = mediaPlayer.settings.getMode("loop");
            repeatLabel.Enabled = repeat;
        }

        private void initFormEvents()
        {
            this.KeyPress += new KeyPressEventHandler(KeyPressed);
            this.KeyUp += new KeyEventHandler(KeyReleased);
            this.recogTimer.Elapsed += new ElapsedEventHandler(OnTimer);
            mediaPlayer.PlayStateChange += new AxWMPLib._WMPOCXEvents_PlayStateChangeEventHandler(PlayerStateChange);
        }

        private void generateXML()
        {
            Console.WriteLine("Generating Grammar Files");

            FileStream artistFile = new FileStream(artistXML, FileMode.Create, FileAccess.Write);
            StreamWriter artistWriter = new StreamWriter(artistFile);

            FileStream albumFile = new FileStream(albumXML, FileMode.Create, FileAccess.Write);
            StreamWriter albumWriter = new StreamWriter(albumFile);

            FileStream songFile = new FileStream(songXML, FileMode.Create, FileAccess.Write);
            StreamWriter songWriter = new StreamWriter(songFile);

            WMPLib.IWMPPlaylist list = mediaPlayer.mediaCollection.getAll();

            SortedList<string, string> artistList = new SortedList<string, string>();
            SortedList<string, string> albumList = new SortedList<string, string>();
            SortedList<string, string> songList = new SortedList<string, string>();

            writeHeading(artistWriter);
            writeHeading(albumWriter);
            writeHeading(songWriter);

            for (int i = 0; i < list.count; i++)
            {
                string album = list.get_Item(i).getItemInfo("Album").ToLower().Trim();
                string artist = list.get_Item(i).getItemInfo("Artist").ToLower().Trim();
                string song = list.get_Item(i).getItemInfo("Name").ToLower().Trim();

                string cleanalbum = cleanString(album);
                string cleanartist = cleanString(artist);
                string cleansong = cleanString(song);

                // must have a valid album, artist, name to be a song, this weeds out the radio stations built
                // into wmp.
                if (cleanalbum != "" && cleanartist != "" && cleansong != "")
                {
                    try
                    {
                        artistList.Add(cleanartist, artist);
                    }
                    catch (ArgumentException)
                    {
                        //Console.WriteLine(artist + " already listed.");
                    }

                    try
                    {
                        albumList.Add(cleanalbum, album);
                    }
                    catch (ArgumentException)
                    {
                        //Console.WriteLine(album + " already listed.");
                    }

                    try
                    {
                        songList.Add(cleansong, song);
                    }
                    catch (ArgumentException)
                    {
                        //Console.WriteLine(song + " already listed.");
                    }
                }
            }

            foreach (KeyValuePair<string, string> kvp in artistList)
            {
                if (kvp.Key != "")
                    writeElement(artistWriter, kvp.Key, kvp.Value);
            }

            foreach (KeyValuePair<string, string> kvp in albumList)
            {
                if (kvp.Key != "")
                    writeElement(albumWriter, kvp.Key, kvp.Value);
            }

            foreach (KeyValuePair<string, string> kvp in songList)
            {
                if (kvp.Key != "")
                    writeElement(songWriter, kvp.Key, kvp.Value);
            }

            writeFooter(artistWriter);
            writeFooter(albumWriter);
            writeFooter(songWriter);

            artistWriter.Close();
            albumWriter.Close();
            songWriter.Close();

            Console.WriteLine("Done Generating Grammar Files");
        }

        private void writeHeading(StreamWriter writer)
        {
            writer.WriteLine("<!-- THIS FILE IS AUTOGENERATED - DO NOT EDIT -->\r\n\r\n"
                + "<!-- The grammar tag surrounds the entire CFG description\n"
                + "Specify the language of the grammar as English-American ('409') -->\n"
                + "\n"
                + "<!-- +word = high confidence required, -word = low confidence required\n"
                + "<P> is required</P>   <O> is Optional </o> <O>...</O> allows for \n"
                + "random optional words   -->\n\n"
                + "<GRAMMAR LANGID=\"409\">\r\n"
                + "  ");
        }

        private void writeElement(StreamWriter writer, string speech, string id)
        {
            id = id.Replace("&", "&amp;");
            id = id.Replace("\"", "&quot;");
            id = id.Replace("<", "&lt;");
            id = id.Replace(">", "&gt;");

            writer.WriteLine(
                "  <RULE NAME=\"" + id + "\" TOPLEVEL=\"ACTIVE\">\n"
                + "    <P>" + speech + "</P>\n"
                + "  </RULE>\n");
        }

        private void writeFooter(StreamWriter writer)
        {
            writer.WriteLine("<!-- End of Grammar definition -->\n"
                + "</GRAMMAR>");
        }

        private string cleanString(string str)
        {
            // some special cases
            str = str.Replace('é', 'e');
            
            char[] ary = str.ToCharArray();

            for (int i = 0; i < ary.Length; i++)
            {
                if (ary[i] > 'z' || (ary[i] < 'a' && ary[i] != ' ' && (ary[i] < '0' || ary[i] > '9')))
                    ary[i] = '#';
            }

            String s = new String(ary);

            s = s.Replace("#", "");

            s = ordinalNumbers(s);

            // Console.WriteLine(str + " ====> " + s);

            return s;
        }

        // FIXME: This is a horrible way to do this.
        // (might try implemeting: http://www.developer.com/net/asp/article.php/3288031)
        private string ordinalNumbers(string s)
        {
            s = s.Replace("1st", "first");
            s = s.Replace("2nd", "second");
            s = s.Replace("3rd", "third");
            s = s.Replace("4th", "fourth");
            s = s.Replace("5th", "fifth");
            s = s.Replace("6th", "sixth");
            s = s.Replace("7th", "seventh");
            s = s.Replace("8th", "eighth");
            s = s.Replace("9th", "nineth");
            s = s.Replace("10th", "tenth");

            return s;
        }

        private void loadGrammar(string FileName)
        {
            try
            {
                grammar.CmdLoadFromFile(FileName, SpeechLib.SpeechLoadOption.SLODynamic);
                grammar.CmdSetRuleIdState(0, SpeechRuleState.SGDSActive);
            }
            catch
            {
                Console.WriteLine("Error Loading Grammar: " + FileName);
                return;
            }

            Console.WriteLine("Grammar Loaded: " + FileName);
        }

        // Text to Speech
        private void speak(string s)
        {
            bool temp = recogEnabled;
            //TODO: Test that recognition isnt on when the computer is speaking
            recogEnabled = false;
            SpVoice voice = new SpVoice();
            voice.Speak(s, SpeechVoiceSpeakFlags.SVSFDefault);
            recogEnabled = temp;
        }

        private void KeyPressed(Object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == ' ')
            {
                if(!recogEnabled)
                    mediaPlayer.settings.volume = mediaPlayer.settings.volume / 4;

                recogEnabled = true;
            }
        }

        private void PlayerStateChange(Object Sender, AxWMPLib._WMPOCXEvents_PlayStateChangeEvent e)
        {
            if (e.newState == 9) // mediaTransitioned
            {
                // fix this
                if (repeat && !commandRepeat)
                {
                    // Deal w/ next and previous! - Allow them to override this
                    mediaPlayer.Ctlcontrols.playItem(repeatSong);
                }

                commandRepeat = false;
            }
        }

        private void KeyReleased(Object sender, KeyEventArgs e)
        {
            if (e.KeyValue == 32)
            {
                mediaPlayer.settings.volume = volume;
                recogEnabled = false;
            }
        }

        private void OnTimer(object sender, ElapsedEventArgs e)
        {
            recogTimer.Stop();
            Console.Write("Recognize Timeout: ");
            loadGrammar(playerXML);
        }

        private void Reco_Event(int StreamNumber, object StreamPosition, SpeechRecognitionType RecognitionType, ISpeechRecoResult Result)
        {
            if (recogEnabled)
            {
                commandRepeat = false;

                recognized.Text = Result.PhraseInfo.GetText(0, -1, true);

                string said = recognized.Text;

                string phrase = Result.PhraseInfo.Rule.Name;


                if (listenMode == COMMAND)
                {
                    // These cases come from playerXML, artistXML, and albumXML
                    switch (phrase)
                    {
                        //generic
                        case "mainmenu":
                            speak("Main menu");
                            loadGrammar(playerXML);
                            break;

                        // commands
                        case "playsong":
                            listenMode = SONG;
                            loadGrammar(songXML);
                            recogTimer.Start();
                            break;

                        case "playartist":
                            listenMode = ARTIST;
                            loadGrammar(artistXML); 
                            recogTimer.Start();
                            break;

                        case "playalbum":
                            listenMode = ALBUM;
                            loadGrammar(albumXML);
                            recogTimer.Start();
                            break;

                        case "playall":
                            mediaPlayer.currentPlaylist = mediaPlayer.mediaCollection.getAll();
                            break;


                        case "stop":
                            mediaPlayer.Ctlcontrols.stop();
                            break;

                        case "previous":
                            commandRepeat = true;
                            mediaPlayer.Ctlcontrols.previous();
                            break;

                        case "next":
                            commandRepeat = true;
                            mediaPlayer.Ctlcontrols.next();

                            break;

                        case "play":
                        case "pause":
                            if (!paused)
                            {
                                mediaPlayer.Ctlcontrols.pause();
                                paused = true;
                            }
                            else
                            {
                                mediaPlayer.Ctlcontrols.play();
                                paused = false;
                            }
                            break;

                        case "mute":
                            mediaPlayer.settings.mute = !mediaPlayer.settings.mute;
                            break;

                        case "volumeup":
                            volume = volume + 25;
                            break;

                        case "volumedown":
                            volume = volume - 25;
                            break;

                        case "random":
                        case "shuffle":
                            if (!mediaPlayer.settings.getMode("shuffle"))
                            {
                                mediaPlayer.settings.setMode("shuffle", true);
                                shuffleLabel.Enabled = true;
                            }
                            else
                            {
                                mediaPlayer.settings.setMode("shuffle", false);
                                shuffleLabel.Enabled = false;
                            }
                            break;

                        case "loop":
                            if (!mediaPlayer.settings.getMode("loop"))
                            {
                                mediaPlayer.settings.setMode("loop", true);
                                loopLabel.Enabled = true;
                            }
                            else
                            {
                                mediaPlayer.settings.setMode("loop", false);
                                loopLabel.Enabled = false;
                            }
                            break;

                        //TODO: This doesnt work!!!!
                        case "repeat":
                            if (!repeat)
                            {
                                repeatSong = mediaPlayer.currentMedia;
                                repeat = true;
                                repeatLabel.Enabled = true;
                            }
                            else
                            {
                                repeatSong = null;
                                repeat = false;
                                repeatLabel.Enabled = false;
                            }
                            break;
                    }
                }
                else if (listenMode == ALBUM)
                {
                    commandRepeat = true;
                    mediaPlayer.currentPlaylist = mediaPlayer.mediaCollection.getByAlbum(phrase);
                    listenMode = COMMAND;
                    loadGrammar(playerXML);
                    recogTimer.Stop();
                }
                else if (listenMode == ARTIST)
                {
                    commandRepeat = true;
                    mediaPlayer.currentPlaylist = mediaPlayer.mediaCollection.getByAuthor(phrase);
                    listenMode = COMMAND;
                    loadGrammar(playerXML);
                    recogTimer.Stop();
                }
                else if (listenMode == SONG)
                {
                    commandRepeat = true;
                    mediaPlayer.currentPlaylist = mediaPlayer.mediaCollection.getByName(phrase);
                    listenMode = COMMAND;
                    loadGrammar(playerXML);
                    recogTimer.Stop();
                }
            }
        }
    }
}
